import asyncio
import logging
import json
import os
import time
from pathlib import Path
from datetime import datetime
from threading import Lock
from playwright.async_api import async_playwright, TimeoutError as PlaywrightTimeoutError
import requests

# 配置日志（仅输出到文件）
def setup_logger():
    today = datetime.now().strftime("%Y%m%d")
    log_filename = f"monitor_{today}.log"
    
    log_format = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    
    file_handler = logging.FileHandler(log_filename, encoding='utf-8')
    file_handler.setFormatter(log_format)
    
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)
    if logger.hasHandlers():
        logger.handlers.clear()
    logger.addHandler(file_handler)
    
    return logger

# 记录配置文件最后修改时间和线程锁
config_last_modified = 0
config_lock = Lock()  # 确保配置更新时的线程安全
account_sessions = {}  # {account_name: {"session": (page, context), "task": task, "config": account_config}}
stop_event = None  # 全局停止事件

logger = setup_logger()

# 读取配置文件
def load_config(config_path="config.json"):
    try:
        if not Path(config_path).exists():
            raise FileNotFoundError(f"配置文件 {config_path} 不存在")
        
        with open(config_path, "r", encoding="utf-8") as f:
            config = json.load(f)
        
        required_keys = ["global", "accounts"]
        for key in required_keys:
            if key not in config:
                raise ValueError(f"配置文件缺少必要字段: {key}")
        
        logger.info("配置文件加载成功")
        return config
    except Exception as e:
        logger.error(f"加载配置文件失败: {str(e)}")
        raise

config = load_config()
config_last_modified = os.path.getmtime("config.json")  # 记录初始修改时间
GLOBAL_CONFIG = config["global"]
ACCOUNTS = config["accounts"]

async def handle_conversation(page, account_name, replied_users, conversation_index):
    """处理指定索引的会话，支持特殊关键词匹配"""
    try:
        # 1. 定位消息列表容器
        list_container = page.locator('[data-e2e="listDlg-container"]')
        if await list_container.count() == 0:
            logger.warning(f"账号 {account_name} 未找到消息列表容器，跳过处理")
            return
        
        # 2. 定位所有会话项
        conversation_items = list_container.locator('[data-e2e="conversation-item"]')
        item_count = await conversation_items.count()
        
        if conversation_index >= item_count:
            logger.warning(f"会话索引 {conversation_index} 超出范围，共有 {item_count} 个会话")
            return
        
        # 3. 获取会话项
        item = conversation_items.nth(conversation_index)
        
        # 获取账号配置和关键词
        account_config = account_sessions[account_name]["config"]
        trigger_keyword = account_config.get("trigger_keyword")
        
        # 获取会话描述文本用于关键词匹配
        desc_element = item.locator('div:nth-child(1) > div:nth-child(2) > div:nth-child(2) > div:nth-child(1) pre')
        desc_text = ""
        if await desc_element.count() > 0:
            desc_text = await desc_element.inner_text(timeout=500)
            desc_text = desc_text.strip()
        
        is_special = trigger_keyword and trigger_keyword in desc_text
        
        # 新增：检查会话是否有未读消息（semi-badge-count元素存在）
        badge_count = item.locator('.semi-badge-count')
        has_unread = await badge_count.count() > 0
        
        # 如果不是特殊会话且没有未读消息，则跳过
        if not is_special and not has_unread:
            return

        # 滚动到会话项
        try:
            await item.scroll_into_view_if_needed(timeout=800)
        except PlaywrightTimeoutError:
            snapshot_path = f"snapshots/{account_name}_scroll_failed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            os.makedirs(os.path.dirname(snapshot_path), exist_ok=True)
            await page.screenshot(path=snapshot_path)
            logger.warning(f"账号 {account_name} 滚动会话项失败，已保存快照：{snapshot_path}，跳过")
            return
        
        # 点击会话项
        try:
            await item.click(timeout=800)
            logger.info(f"账号 {account_name} 已点击第 {conversation_index+1} 个会话")
            await asyncio.sleep(0.5)  # 等待聊天窗口加载
        except PlaywrightTimeoutError:
            snapshot_path = f"snapshots/{account_name}_click_failed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            await page.screenshot(path=snapshot_path)
            logger.warning(f"账号 {account_name} 点击会话项失败，已保存快照：{snapshot_path}，跳过")
            return

        # 4. 获取当前聊天用户的昵称
        try:
            # 定位对话详情主容器
            conversation_detail_container = page.locator('[data-mask="conversaton-detail-content"]')
            
            # 检查容器是否存在
            if await conversation_detail_container.count() == 0:
                logger.warning(f"账号 {account_name} 未找到对话详情容器，跳过")
                return
            
            # 在容器内查找包含昵称的元素
            nickname_element = conversation_detail_container.locator('span').first
            
            # 等待元素加载并获取文本
            user_nickname = await nickname_element.inner_text(timeout=800)
            
            # 过滤空文本或空格
            user_nickname = user_nickname.strip()
            if not user_nickname:
                logger.warning(f"账号 {account_name} 获取到空昵称，跳过")
                return
            logger.info(f"当前聊天用户：{user_nickname}")

            # 判断是否为"自己人"（配置文件中的账号名称）
            account_names = [acc["name"] for acc in ACCOUNTS]
            if user_nickname in account_names:
                logger.info(f"检测到当前聊天用户 {user_nickname} 是自己人（配置文件中的账号），跳过回复流程")
                return  # 直接返回，不执行后续回复逻辑
            
            # 初始化用户回复状态
            today = datetime.now().strftime("%Y-%m-%d")
            if user_nickname not in replied_users or replied_users[user_nickname]["date"] != today:
                replied_users[user_nickname] = {
                    "date": today, 
                    "count": 0,
                    "special_replied": False  # 记录是否已发送特殊回复
                }
                
        except PlaywrightTimeoutError:
            snapshot_path = f"snapshots/{account_name}_nickname_timeout_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            os.makedirs(os.path.dirname(snapshot_path), exist_ok=True)
            await page.screenshot(path=snapshot_path)
            logger.warning(f"账号 {account_name} 未找到昵称元素（超时），已保存快照：{snapshot_path}，跳过")
            return
        except Exception as e:
            logger.error(f"账号 {account_name} 获取用户昵称时出错：{str(e)}")
            return

        # 5. 判断是否需要特殊回复
        if not replied_users[user_nickname]["special_replied"]:
            trigger_reply = account_config.get("trigger_reply")
            
            # 只有存在特殊回复配置时才进行关键词匹配
            if trigger_keyword and trigger_reply:
                # 获取最新消息内容（仅在需要特殊回复检查时执行）
                await asyncio.sleep(1.2)  # 等待聊天窗口加载
                
                # 获取最近2条用户发送的消息（justify-content: right）
                right_aligned_messages = page.locator('div[style*="justify-content: right"]')
                message_count = await right_aligned_messages.count()
                
                latest_messages = []
                # 最多获取最近2条消息
                for i in range(min(2, message_count)):
                    message_element = right_aligned_messages.nth(i)
                    content_element = message_element.locator('[data-e2e="msg-item-content"]')
                    if await content_element.count() > 0:
                        # 处理pre元素可能存在多个的情况，只取第一个非空文本
                        pre_elements = content_element.locator('pre')
                        pre_count = await pre_elements.count()
                        msg_text = ""
                        
                        if pre_count > 0:
                            # 遍历所有pre元素，取第一个非空文本
                            for j in range(pre_count):
                                pre_text = await pre_elements.nth(j).inner_text(timeout=500)
                                pre_text = pre_text.strip()
                                if pre_text:  # 只保留非空文本
                                    msg_text = pre_text
                                    break
                        else:
                            # 直接获取内容容器内文本
                            msg_text = (await content_element.inner_text(timeout=500)).strip()
                        if msg_text:  # 只添加非空消息
                            latest_messages.append(msg_text)
                
                logger.info(f"账号 {account_name} 在会话描述中：'{desc_text}'，最近2条官方回复：{latest_messages}")
                
                # 检查关键词匹配
                should_use_special_reply = False
                if trigger_keyword in desc_text:
                    should_use_special_reply = True
                    logger.info(f"账号 {account_name} 在会话描述中检测到特殊关键词 '{trigger_keyword}'")
                else:
                    # 在最近2条消息中查找关键词
                    for msg in latest_messages:
                        if trigger_keyword in msg:
                            should_use_special_reply = True
                            logger.info(f"账号 {account_name} 在最近消息中检测到特殊关键词 '{trigger_keyword}'")
                            break
                
                # 执行特殊回复流程
                if should_use_special_reply:
                    try:
                        chat_input = page.locator('.im-richtext-container .public-DraftEditor-content')
                        await asyncio.gather(
                            chat_input.wait_for(state='visible', timeout=800),
                            chat_input.click(timeout=800)
                        )
                        
                        await chat_input.type(trigger_reply, delay=0)
                        await chat_input.press('Enter', timeout=800)
                        await asyncio.sleep(0.1)
                        await chat_input.press('Enter', timeout=800)
                        await asyncio.sleep(0.8)
                        
                        replied_users[user_nickname]["special_replied"] = True
                        logger.info(f"账号 {account_name} 对 {user_nickname} 发送特殊回复：{trigger_reply}")
                        snapshot_path = f"snapshots/{account_name}_to_{user_nickname}_special_replied_succeed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
                        os.makedirs(os.path.dirname(snapshot_path), exist_ok=True)
                        await page.screenshot(path=snapshot_path)
                        logger.info(f"账号 {account_name} 对 {user_nickname} 发送特殊回复成功，已保存快照：{snapshot_path}")
                        return
                        
                    except PlaywrightTimeoutError:
                        logger.error(f"账号 {account_name} 特殊回复输入框操作超时，发送失败")
                        return
                    except Exception as e:
                        logger.error(f"账号 {account_name} 发送特殊回复失败：{str(e)}")
                        return

        # 6. 普通会话处理逻辑
        today = datetime.now().strftime("%Y-%m-%d")
        if user_nickname not in replied_users or replied_users[user_nickname]["date"] != today:
            replied_users[user_nickname] = {"date": today, "count": 0}
        
        current_reply_count = replied_users[user_nickname]["count"]
        max_replies = GLOBAL_CONFIG["max_daily_replies"]
        if current_reply_count >= max_replies:
            logger.info(f"账号 {account_name} 对 {user_nickname} 的回复已达每日上限（{max_replies}次），跳过")
            return
        
        # 7. 发送普通回复消息
        next_reply_count = current_reply_count + 1
        welcome_msg = GLOBAL_CONFIG["welcome_messages"].get(
            str(next_reply_count),
            GLOBAL_CONFIG["welcome_messages"][str(max_replies)]
        )
        
        try:
            chat_input = page.locator('.im-richtext-container .public-DraftEditor-content')
            await asyncio.gather(
                chat_input.wait_for(state='visible', timeout=800),
                chat_input.click(timeout=800)
            )
            
            await chat_input.type(welcome_msg, delay=0)
            await chat_input.press('Enter', timeout=800)
            await asyncio.sleep(0.1)
            await chat_input.press('Enter', timeout=800)
            await asyncio.sleep(0.8)
            
            replied_users[user_nickname]["count"] = next_reply_count
            logger.info(f"账号 {account_name} 对 {user_nickname} 发送第 {next_reply_count}/{max_replies} 条回复：{welcome_msg}")
            snapshot_path = f"snapshots/{account_name}_to_{user_nickname}_normal_replied_succeed_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            os.makedirs(os.path.dirname(snapshot_path), exist_ok=True)
            await page.screenshot(path=snapshot_path)
            logger.info(f"账号 {account_name} 对 {user_nickname} 发送普通回复成功，已保存快照：{snapshot_path}")
            return
        except PlaywrightTimeoutError:
            logger.error(f"账号 {account_name} 输入框操作超时，发送失败")
        except Exception as e:
            logger.error(f"账号 {account_name} 发送消息失败：{str(e)}")
    
    except Exception as e:
        logger.error(f"账号 {account_name} 处理会话时发生错误：{str(e)}")

async def monitor_page_changes(page, account_name, stop_event, account_config):
    """监控页面变化，监听会话列表变化"""
    logger.info(f"开始监控账号 {account_name} 的页面变化...")
    
    replied_users = {}  # {用户昵称: {"date": "YYYY-MM-DD", "count": 0}}
    monitoring = True
    max_attempts = 2
    
    try:
        # 页面加载与登录状态验证
        is_logged_in = False
        for attempt in range(max_attempts):
            try:
                # 等待头部菜单区域加载完成（抖音页面核心标识）
                header_menu = page.locator("#douyin-header-menuCt")
                await header_menu.wait_for(state="visible", timeout=5000)
                logger.info(f"账号 {account_name} 页面头部加载成功（尝试 {attempt+1}/{max_attempts}）")
                
                # 检测登录状态：未登录会显示"登录"按钮，已登录会显示用户头像区域
                login_button = page.locator('button:has-text("登录")')
                is_logged_in = await login_button.count() == 0
                
                if is_logged_in:
                    logger.info(f"账号 {account_name} 已成功登录")
                    break 
                else:
                    logger.warning(f"账号 {account_name} 未登录，尝试重新加载...")
                    await page.reload(timeout=5000)
                    
            except PlaywrightTimeoutError:
                if attempt < max_attempts - 1:
                    logger.warning(f"账号 {account_name} 页面加载超时，重试中（尝试 {attempt+1}/{max_attempts}）")
                    await page.reload(timeout=5000)
                else:
                    raise Exception(f"账号 {account_name} 页面加载失败，已尝试 {max_attempts} 次")
        
        if not is_logged_in:  
            # 发送 iOS 推送通知登录失败
            send_ios_push(
                key="SjrCnTXx3tLNM7sNoVLXvL",  # 推送密钥
                title=f"抖音账号监控失败通知",
                message=f"账号 {account_name} 登录验证失败，无法继续监控"
            )
            # 不再抛出异常，使用return优雅退出
            logger.info(f"账号 {account_name} 因登录失败，退出监控任务")
            return
            
        # 鼠标悬浮在私信DIV上
        message_area_found = await hover_message_area(page, account_name)
        if not message_area_found:
            logger.error(f"因未找到私信区域，账号 {account_name} 监控任务终止")
            return
        
        # 2. 初始化定位器
        list_container = page.locator('[data-e2e="listDlg-container"]')
        conversation_items = list_container.locator('[data-e2e="conversation-item"]')
        
        # 3. 初始会话检测
        initial_count = await conversation_items.count()
        logger.info(f"账号 {account_name} 初始检测到 {initial_count} 个会话")
        
        # 4. 事件驱动循环 - 监听会话列表变化和未读消息
        logger.info(f"账号 {account_name} 启动会话列表变化监听模式")
        while monitoring and not stop_event.is_set():
            if account_name not in account_sessions:
                logger.info(f"账号 {account_name} 会话已关闭，停止监控")
                break
            
            try:
                # 等待会话列表变化或未读消息出现
                await wait_for_conversation_change(list_container, conversation_items, account_config)
                
                # 刷新会话项定位器
                conversation_items = list_container.locator('[data-e2e="conversation-item"]')
                new_count = await conversation_items.count()
                
                # logger.info(f"账号 {account_name} 检测到会话变化，当前会话数量: {new_count}")
                
                # 处理所有会话（从最新到最旧）
                for i in range(min(3, new_count)):
                    await handle_conversation(page, account_name, replied_users, i)

                await asyncio.sleep(0.1)  
                
            except asyncio.TimeoutError:
                logger.debug(f"账号 {account_name} 10秒内无会话变化，继续监听")
            except Exception as e:
                logger.error(f"账号 {account_name} 会话监听出错: {str(e)}")
        
    except Exception as e:
        # 这里只捕获其他未预期的异常
        logger.error(f"账号 {account_name} 监控出错：{str(e)}")
    finally:
        monitoring = False
        if account_name in account_sessions:
            del account_sessions[account_name]
        logger.info(f"账号 {account_name} 监控任务已停止")

async def wait_for_conversation_change(list_container, conversation_items, account_config):
    """监听会话列表变化或未读消息出现"""
    
    # 创建未读消息标记的定位器
    unread_badges = list_container.locator('.semi-badge-count')
    
    # 创建任务等待列表变化或未读消息出现
    try:

        # 等待未读消息出现
        unread_task = asyncio.create_task(
            unread_badges.first.wait_for(state='visible', timeout=20000)
        )
        # 等待列表容器变化
        list_task = asyncio.create_task(
            list_container.wait_for(state='visible', timeout=20000)
        )
        # 等待任一任务完成
        done, pending = await asyncio.wait(
            [list_task, unread_task],
            return_when=asyncio.FIRST_COMPLETED
        )
        
        # 取消未完成的任务
        for task in pending:
            task.cancel()
            
        # 返回结果
        # if unread_task in done:
        #     logger.info("检测到未读消息标记")
        # else:
        #     logger.info("检测到会话列表变化")
            
    except asyncio.TimeoutError:
        # 超时处理，继续循环
        raise
    except Exception as e:
        logger.error(f"监听会话变化出错: {str(e)}")
        raise

def send_ios_push(key, title, message):
    """发送 iOS 推送通知"""
    url = f"https://api.day.app/{key}/{title}/{message}"
    try:
        response = requests.get(url)
        response.raise_for_status()  # 检查请求是否成功
        return response.text
    except requests.exceptions.RequestException as e:
        # 记录错误日志
        logger.error(f"发送 iOS 推送失败: {e}")
        raise

async def hover_message_area(page, account_name):
    """定位并悬浮/点击到私信区域"""
    # 定义多种可能的选择器
    message_selectors = [
        'li.hA2I3Wjd.FvEk43aG div[data-e2e="im-entry"]',  # 原始选择器
        'div[data-e2e="im-entry"]',  # 简化选择器
        '//p[contains(text(), "私信")]/ancestor::div[contains(@class, "kxLu9xNn")]',  # XPath选择器
        'a[href*="/message/"]',  # 基于链接的选择器
    ]
    
    try:
        element_found = False
        # 尝试每个选择器
        for selector in message_selectors:
            try:
                message_div = page.locator(selector)
                if await message_div.count() > 0:
                    logger.info(f"账号 {account_name} 使用选择器 '{selector}' 定位到私信区域")
                    
                    # 尝试悬浮和点击
                    try:
                        await asyncio.sleep(2)  # 等待列表加载
                        await message_div.hover(timeout=2000)
                        logger.info(f"账号 {account_name} 成功悬浮到私信区域")
                        # await message_div.click(timeout=2000)
                        # logger.info(f"账号 {account_name} 已点击打开私信列表")
                        element_found = True
                        break
                    except Exception as e:
                        logger.warning(f"账号 {account_name} 交互私信区域失败: {str(e)}")
                else:
                    logger.debug(f"账号 {account_name} 选择器 '{selector}' 未找到元素")
            except Exception as e:
                logger.warning(f"账号 {account_name} 使用选择器 '{selector}' 定位失败: {str(e)}")
        
        # 如果所有选择器都失败，尝试查找页面上包含"私信"文本的元素
        if not element_found:
            try:
                text_elements = page.locator('text=私信')
                count = await text_elements.count()
                if count > 0:
                    element = text_elements.nth(0)  # 选择第一个匹配的元素
                    
                    # 尝试悬浮和点击
                    try:
                        await element.hover()
                        logger.info(f"账号 {account_name} 成功悬浮到包含'私信'文本的元素")
                        
                        await element.click()
                        logger.info(f"账号 {account_name} 已点击包含'私信'文本的元素")
                        await asyncio.sleep(2)  # 等待列表加载
                        
                        element_found = True
                    except Exception as e:
                        logger.warning(f"账号 {account_name} 交互文本元素失败: {str(e)}")
                else:
                    logger.warning(f"页面上未找到包含'私信'文本的元素")
            except Exception as e:
                logger.warning(f"查找'私信'文本元素失败: {str(e)}")
            
    except Exception as e:
        logger.error(f"账号 {account_name} 私信区域交互过程出错: {str(e)}")
    
    if element_found:
        logger.info(f"账号 {account_name} 已成功定位并交互私信区域")
    else:
        logger.warning(f"账号 {account_name} 所有方法均未能定位到私信区域")
    
    return element_found


def parse_cookies(cookie_string):
    """解析抖音Cookie字符串为Playwright兼容格式"""
    cookies = []
    cookie_pairs = [pair.strip() for pair in cookie_string.split(';') if pair.strip()]
    
    for pair in cookie_pairs:
        if '=' not in pair:
            continue
        
        name, value = pair.split('=', 1)
        name = name.strip()
        value = value.strip()
        
        cookie = {
            "name": name,
            "value": value,
            "path": "/",
            "httpOnly": False,
            "secure": False,
            "sameSite": "Lax"
        }
        
        if name in [
            "sid_guard", "sessionid", "uid_tt", "sid_tt",
            "d_ticket", "n_mh", "ttwid", "odin_tt",
            "passport_auth_status", "passport_assist_user",
            "msToken", "ttcid", "ttsectoken"
        ]:
            cookie["domain"] = ".douyin.com"
        
        elif name in [
            "passport_csrf_token", "passport_csrf_token_default",
            "passport_mfa_token", "sid_ucp_v1"
        ]:
            cookie["domain"] = "passport.douyin.com"
        
        else:
            cookie["domain"] = ".douyin.com"
        
        if name in ["sid_guard", "sessionid", "uid_tt", "ttwid"]:
            cookie["expires"] = int(time.time() + 30 * 86400)
        else:
            cookie["expires"] = -1
        
        cookies.append(cookie)
    
    return cookies

async def create_session(p, cookie_string, account_name, target_url):
    # 启动前删除旧的用户数据目录
    user_data_dir = f"./user_data_{account_name}"
    if os.path.exists(user_data_dir):
        import shutil
        try:
            shutil.rmtree(user_data_dir)  # 递归删除文件夹
            logger.info(f"已清除旧会话数据: {user_data_dir}")
        except Exception as e:
            logger.warning(f"删除旧会话数据失败: {str(e)}")
    # 浏览器配置
    context = await p.chromium.launch_persistent_context(
        user_data_dir=user_data_dir,
        headless=True,
        args=[
            "--disable-blink-features=AutomationControlled",
            "--no-sandbox",
            "--disable-dev-shm-usage",
            "--blink-settings=imagesEnabled=false",
            "--disable-extensions",
            "--disable-plugins",
            "--disable-notifications",
            "--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.158 Safari/537.36",
        ]
    )
    
    await context.add_cookies(parse_cookies(cookie_string))
    
    page = await context.new_page()
    await page.goto(target_url, timeout=15000) 
    await page.wait_for_load_state("domcontentloaded", timeout=10000) 
    
    if "login" in page.url:
        raise Exception(f"账号 {account_name} Cookie失效，未登录成功")
    
    logger.info(f"账号 {account_name} 会话创建成功，当前页面：{page.url}")
    return page, context

# 配置热加载
def check_and_reload_config(config_path="config.json"):
    global config, GLOBAL_CONFIG, ACCOUNTS, config_last_modified, account_sessions
    
    try:
        current_modified = os.path.getmtime(config_path)
        if current_modified <= config_last_modified:
            return False
        
        with config_lock:
            new_config = load_config(config_path)
            
            # 对比账号配置，处理变更
            old_accounts = {acc["name"]: acc for acc in ACCOUNTS}
            new_accounts = {acc["name"]: acc for acc in new_config["accounts"]}
            
            # 处理删除的账号
            for name in set(old_accounts.keys()) - set(new_accounts.keys()):
                asyncio.create_task(close_account_session(name))
            
            # 处理新增或修改的账号
            for name, new_acc in new_accounts.items():
                old_acc = old_accounts.get(name)
                if not old_acc or old_acc["cookie_string"] != new_acc["cookie_string"]:
                    if old_acc:
                        asyncio.create_task(close_account_session(name))
                    asyncio.create_task(create_and_start_account_session(new_acc))
            
            # 更新全局配置
            config = new_config
            GLOBAL_CONFIG = new_config["global"]
            ACCOUNTS = new_config["accounts"]
            config_last_modified = current_modified
            logger.info("配置文件已热加载，账号会话已更新")
            return True
            
    except Exception as e:
        logger.error(f"热加载配置失败: {str(e)}")
        return False

# 会话管理
async def close_account_session(account_name):
    if account_name in account_sessions:
        page, context = account_sessions[account_name]["session"]
        
        # 标记会话为关闭状态
        if "monitoring" in account_sessions[account_name]:
            account_sessions[account_name]["monitoring"] = False
        
        # 关闭上下文
        try:
            await context.close()
        except Exception as e:
            logger.warning(f"账号 {account_name} 上下文关闭失败: {str(e)}")
        
        # 从会话列表中移除
        del account_sessions[account_name]
        logger.info(f"账号 {account_name} 会话已关闭")

async def create_and_start_account_session(account_config, p=None):
    global account_sessions, stop_event
    
    try:
        if p is None:
            p = getattr(main, "playwright_instance", None)
            if p is None:
                logger.error("无法获取playwright实例，账号会话创建失败")
                return
        
        logger.info(f"开始创建账号 {account_config['name']} 的会话...")
        page, context = await create_session(
            p, 
            account_config["cookie_string"], 
            account_config["name"], 
            GLOBAL_CONFIG["target_url"]
        )
        
        # 创建并启动监控任务
        logger.info(f"准备启动账号 {account_config['name']} 的监控任务...")
        task = asyncio.create_task(monitor_page_changes(
            page, 
            account_config["name"], 
            stop_event,
            account_config
        ))
        
        # 存储会话信息
        account_sessions[account_config["name"]] = {
            "session": (page, context),
            "task": task,
            "config": account_config,
            "monitoring": True
        }
        
        logger.info(f"账号 {account_config['name']} 新会话已创建并启动监控")
        
    except Exception as e:
        logger.error(f"创建账号 {account_config['name']} 会话失败: {str(e)}")

# 配置监听任务
async def config_monitor_task(stop_event, check_interval=10):
    global logger, config_last_modified
    last_log_date = datetime.now().strftime("%Y%m%d")  # 记录当前日志日期
    
    while not stop_event.is_set():
        # 检查配置文件是否有更新
        check_and_reload_config()
        
        # 检查日期是否变化，如果变化则重新初始化日志
        current_date = datetime.now().strftime("%Y%m%d")
        if current_date != last_log_date:
            # 日期已变更，重新初始化日志系统
            logger.info(f"日期变更，切换日志文件至: monitor_{current_date}.log")
            
            # 创建新的 logger 实例
            new_logger = setup_logger()
            
            # 更新全局 logger
            logging.root.handlers = new_logger.handlers
            logger = new_logger
            
            # 记录日志切换成功
            logger.info(f"日志文件已成功切换至: monitor_{current_date}.log")
            last_log_date = current_date
        
        await asyncio.sleep(check_interval)

# 主函数
async def main():
    global stop_event
    
    target_url = GLOBAL_CONFIG["target_url"]
    stop_event = asyncio.Event()
    
    async with async_playwright() as p:
        main.playwright_instance = p
        
        tasks = []
        
        try:
            # 启动配置监控任务
            config_task = asyncio.create_task(config_monitor_task(stop_event))
            tasks.append(config_task)
            
            # 初始化账号会话
            logger.info("开始初始化账号会话...")
            for account in ACCOUNTS:
                logger.info(f"准备创建账号 {account['name']} 的会话...")
                await create_and_start_account_session(account, p)
            
            logger.info("所有会话已启动，开始监控页面变化...")
            logger.info(f"每日回复限制: 每个用户 {GLOBAL_CONFIG['max_daily_replies']} 条消息")
            logger.info(f"动态消息规则: {GLOBAL_CONFIG['welcome_messages']}")
            logger.info("按 Ctrl+C 停止监控")
            
            # 保持主循环运行
            await asyncio.Event().wait()
            
        except KeyboardInterrupt:
            logger.info("程序已接收到停止信号，正在关闭...")
            stop_event.set()
            
            # 有序关闭所有会话
            for name in list(account_sessions.keys()):
                await close_account_session(name)
                
            # 等待所有任务完成
            await asyncio.gather(*tasks, return_exceptions=True)
            
        finally:
            # 再次确认所有会话已关闭
            for name in list(account_sessions.keys()):
                await close_account_session(name)
                
            logger.info("所有会话已关闭")

if __name__ == "__main__":
    asyncio.run(main()) 